using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using LitJson;
using UnityEngine;
using UnityEngine.Networking;

namespace CGF
{
    public class UploadProgressListener
    {
        private UnityWebRequest request;
        private Action<float> onProgress;
        public UploadProgressListener(UnityWebRequest request, Action<float> onProgress)
        {
            this.request = request;
            this.onProgress = onProgress;
            CGFramework.Instance.updateAction += OnUpdate;
        }
        private void OnUpdate()
        {
            float progress = request.uploadProgress;
            onProgress?.Invoke(progress);
            if (progress >= 1f || request.result != UnityWebRequest.Result.InProgress)
            {
                CGFramework.Instance.updateAction -= OnUpdate;
                if (progress < 1)
                {
                    //确保进度1有且只有1次调用
                    onProgress?.Invoke(1);
                }
            }
        }
    }

    public class HttpRes
    {
        public string url, text, error;
        public bool success;
        public byte[] bytes;

        public HttpRes(string url)
        {
            this.url = url;
        }
    }

    public class GetTask
    {
        private UnityWebRequest request;
        private Action<float> onProgress;

        public async Task<HttpRes> Get(string url, Dictionary<string, string> header, Action<float> onProgress, int timeoutSeconds)
        {
            this.onProgress = onProgress;
            CGFramework.Instance.updateAction += OnUpdate;
            using (request = UnityWebRequest.Get(url))
            {
                request.timeout = timeoutSeconds;
                if (header != null && header.Count > 0)
                {
                    foreach (var h in header)
                    {
                        request.SetRequestHeader(h.Key, h.Value);
                    }
                }
                await CTask.Wait(0.1f);
                await request.SendWebRequest();
                if (!Application.isPlaying) return null;
                await CTask.Wait(0.1f);
                var httpRes = new HttpRes(url);
                if (request.result == UnityWebRequest.Result.Success)
                {
                    httpRes.success = true;
                    httpRes.bytes = request.downloadHandler.data;
                    httpRes.text = request.downloadHandler.text;
                    onProgress(1);
                }
                else
                {
                    httpRes.success = false;
                    httpRes.text = request.downloadHandler.text;
                    httpRes.error = "[" + request.responseCode + "]" + request.error + " " + request.downloadHandler.text;
                }

                if (Debug.canLog)
                {
                    HttpHelper.LogRes("GET", request, httpRes);
                }

                CGFramework.Instance.updateAction -= OnUpdate;
                return httpRes;
            }
        }

        private void OnUpdate()
        {
            if (request != null && onProgress != null)
            {
                if (request.downloadProgress < 1f)
                {
                    onProgress(request.downloadProgress);
                }
            }
        }
    }

    public static class HttpHelper
    {
        //扩展UnityWebRequest方法使其支持await
        public static async CTask GetAwaiter(this AsyncOperation asyncOperation)
        {
            CTask task = CTask.Create();
            asyncOperation.completed += ao => { task.SetResult(); };
            await task;
        }

        public static async CTask<HttpRes> Get(string url, Dictionary<string, string> header = null, Action<float> onProgress = null, int timeoutSeconds = 20)
        {
            string _url = Url(url);
            LogReq("GET", _url, null, header);
            if (onProgress != null)
            {
                var task = new GetTask();
                return await task.Get(_url, header, onProgress, timeoutSeconds);
            }
            else
            {
                using (var request = UnityWebRequest.Get(_url))
                {
                    request.timeout = timeoutSeconds;
                    if (header != null && header.Count > 0)
                    {
                        foreach (var h in header)
                        {
                            request.SetRequestHeader(h.Key, h.Value);
                        }
                    }
                    await CTask.Wait(0.1f);
                    await request.SendWebRequest();
                    if (!Application.isPlaying) return null;
                    await CTask.Wait(0.1f);
                    var httpRes = new HttpRes(url);
                    if (request.result == UnityWebRequest.Result.Success)
                    {
                        httpRes.success = true;
                        httpRes.bytes = request.downloadHandler.data;
                        httpRes.text = request.downloadHandler.text;
                    }
                    else
                    {
                        httpRes.success = false;
                        httpRes.text = request.downloadHandler.text;
                        httpRes.error = "[" + request.responseCode + "]" + request.error + " " + request.downloadHandler.text;
                    }

                    if (Debug.canLog)
                    {
                        LogRes("GET", request, httpRes);
                    }

                    return httpRes;
                }
            }
        }

        public static async CTask<HttpRes> PostJson(string url, JsonData data, Dictionary<string, string> header = null, int timeoutSeconds = 20)
        {
            return await Request(url, "POST", data, header, true, timeoutSeconds);
        }

        public static async CTask<HttpRes> PostForm(string url, Dictionary<string, object> data, Dictionary<string, string> header = null, int timeoutSeconds = 20)
        {
            return await Request(url, "POST", data, header, false, timeoutSeconds);
        }

        public static async CTask<HttpRes> PutJson(string url, JsonData data, Dictionary<string, string> header = null, int timeoutSeconds = 20)
        {
            return await Request(url, "PUT", data, header, true, timeoutSeconds);
        }

        public static async CTask<HttpRes> PutForm(string url, Dictionary<string, object> data, Dictionary<string, string> header = null, int timeoutSeconds = 20)
        {
            return await Request(url, "PUT", data, header, false, timeoutSeconds);
        }

        public static async CTask<HttpRes> Delete(string url, Dictionary<string, string> header = null, int timeoutSeconds = 20)
        {
            return await Request(url, "DELETE", null, header, false, timeoutSeconds);
        }

        private static async CTask<HttpRes> Request(string url, string method, object data, Dictionary<string, string> header, bool isJson, int timeoutSeconds = 20)
        {
            string _url = Url(url);
            LogReq(method, _url, data, header);
            using (UnityWebRequest request = new UnityWebRequest(_url, method))
            {
                request.downloadHandler = new DownloadHandlerBuffer();
                request.timeout = timeoutSeconds;
                byte[] bodyRaw;
                if (isJson)
                {
                    request.SetRequestHeader("Content-Type", "application/json;charset=utf-8");
                    bodyRaw = data != null ? Encoding.UTF8.GetBytes(JsonMapper.ToJson(data)) : null;
                }
                else
                {
                    request.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
                    Dictionary<string, string> dic = new();
                    if (data != null)
                    {
                        foreach (var item in (Dictionary<string, object>)data)
                        {
                            dic.Add(item.Key, item.Value != null ? item.Value.ToString() : "");
                        }
                    }

                    bodyRaw = UnityWebRequest.SerializeSimpleForm(dic);
                }
                if (header != null && header.Count > 0)
                {
                    foreach (var h in header)
                    {
                        request.SetRequestHeader(h.Key, h.Value);
                    }
                }
                request.uploadHandler?.Dispose();
                request.uploadHandler = new UploadHandlerRaw(bodyRaw);
                await CTask.Wait(0.1f);
                await request.SendWebRequest();
                if (!Application.isPlaying) return null;
                await CTask.Wait(0.1f);
                var httpRes = new HttpRes(url);
                if (request.result == UnityWebRequest.Result.Success)
                {
                    httpRes.success = true;
                    httpRes.bytes = request.downloadHandler.data;
                    httpRes.text = request.downloadHandler.text;
                }
                else
                {
                    httpRes.success = false;
                    httpRes.text = request.downloadHandler.text;
                    httpRes.error = "[" + request.responseCode + "]" + request.error + " " + request.downloadHandler.text;
                }

                request.disposeDownloadHandlerOnDispose = true;
                request.disposeUploadHandlerOnDispose = true;
                if (Debug.canLog)
                {
                    LogRes(method, request, httpRes);
                }

                return httpRes;
            }
        }

        public static async CTask<HttpRes> PostFile(string url, Texture2D texture, Dictionary<string, object> data, Dictionary<string, string> header, Action<float> onProgress, int timeoutSeconds = 20)
        {
            string _url = Url(url);
            LogReq("POST File", _url, data, header);
            byte[] textureData = texture.EncodeToJPG();
            List<IMultipartFormSection> formData = new List<IMultipartFormSection>()
            {
                new MultipartFormFileSection("file", textureData, "file.jpg", "image/jpeg"),
            };
            if (data != null)
            {
                foreach (var item in (Dictionary<string, object>)data)
                {
                    formData.Add(new MultipartFormDataSection(item.Key, item.Value != null ? item.Value.ToString() : ""));
                }
            }
            using (UnityWebRequest request = UnityWebRequest.Post(_url, formData))
            {
                request.timeout = timeoutSeconds;
                if (header != null && header.Count > 0)
                {
                    foreach (var h in header)
                    {
                        request.SetRequestHeader(h.Key, h.Value);
                    }
                }
                request.downloadHandler = new DownloadHandlerBuffer();
                new UploadProgressListener(request, onProgress);
                await CTask.Wait(0.1f);
                await request.SendWebRequest();
                if (!Application.isPlaying) return null;
                await CTask.Wait(0.1f);
                var httpRes = new HttpRes(url);
                if (request.result == UnityWebRequest.Result.Success)
                {
                    httpRes.success = true;
                    httpRes.bytes = request.downloadHandler.data;
                    httpRes.text = request.downloadHandler.text;
                }
                else
                {
                    httpRes.success = false;
                    httpRes.text = request.downloadHandler.text;
                    httpRes.error = "[" + request.responseCode + "]" + request.error + " " + request.downloadHandler.text;
                }
                request.disposeDownloadHandlerOnDispose = true;
                request.disposeUploadHandlerOnDispose = true;
                if (Debug.canLog)
                {
                    LogRes("POST file", request, httpRes);
                }
                return httpRes;
            }
        }

        public static void LogReq(string method, string url, object data, Dictionary<string, string> header)
        {
            string log = $"{method} -> {url}";
            if (data != null)
            {
                log += $"\ndata:\n  {Regex.Unescape(JsonMapper.ToJson(data))}";
            }
            if (header != null)
            {
                log += $"\nheader:";
                foreach (var h in header)
                {
                    log += $"\n  {h.Key}: {h.Value}";
                }
            }
            Debug.Log(log + "\n");
        }

        public static void LogRes(string method, UnityWebRequest request, HttpRes httpRes)
        {
            string resText = httpRes.text;
            string contentType = request.GetResponseHeader("Content-Type");
            if (contentType != null && (
                contentType.Contains("application/json") ||
                contentType.Contains("application/xml") ||
                contentType.Contains("text/html") ||
                contentType.Contains("text/plain")))
            {
                resText = Regex.Unescape(resText);
            }
            Debug.Log($"{method} <- {httpRes.url}\n\n{resText}\n");
        }

        private static string Url(string url)
        {
            if (url.Contains("?"))
            {
                return url + "&r=" + DateTime.Now.Ticks;
            }
            else
            {
                return url + "?r=" + DateTime.Now.Ticks;
            }
        }

        /// <summary>
        /// 将文本转为Url编码
        /// </summary>
        public static string URLEncode(string str)
        {
            return UnityWebRequest.EscapeURL(str);
        }

        /// <summary>
        /// 将Url编码转成普通文本
        /// </summary>
        public static string URLDecode(string str)
        {
            return UnityWebRequest.UnEscapeURL(str);
        }
    }
}
